package voting;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import core.Player;

/**
 * <code>VotingManager</code> starts a vote on a <code>Measure</code>, tallies each <code>
 * Client</code>'s vote, declares a winner if a quorum and the requisite success criterion
 * are both met, and then enforces the <code>Command</code> associated with the <code>Measure</code>.  
 * A <code>Measure</code> has a pre-determined number of seconds in which to be voted upon, 
 * and the <code>VotingManager</code> will decide the vote either when everyone in the 
 * <code>GameServer</code> has voted, or when the time duration has expired.
 */
public class VotingManager {
	
	/** The measure currently being voted upon */
	private Measure current;
	/** The GameServer that this VotingManager corresponds to */
	private server.GameServer gameServer;
	/** Maps Accounts to their votes on the current measure */
	private Map<Player, Integer> votes = new HashMap<Player, Integer>();
	/** The timer for the current vote */
	private Timer timer;
	/** An enumeration of possible reasons for a vote failure */
	private enum FailureReason {QUORUM_NOT_MET, VOTE_FAILED};

	private List<Player> participants;
	
	/**
	 * Creates a new VotingManager for the given GameServer.
	 */
	public VotingManager(server.GameServer s)
	{
		gameServer = s;
		timer = new Timer();
		participants = new LinkedList<Player>();
	}
	
	/**
	 * Returns true if there is currently an active vote.  The VotingManager
	 * can only handle one vote at a time.
	 */
	public boolean isActive() {
		return current != null;
	}
	
	/**
	 * Tallies the given Account's vote on the current measure.
	 * @param a
	 * @param v
	 */
	public void tallyVote(Player player, Integer v)
	{
		votes.put(player, v);
		checkSuccess(false);
	}
	
	/**
	 * Adds a player to the list of potential vote participants
	 */
	public void addPlayer(Player p)
	{
		participants.add(p);
	}
	
	/** 
	 * Called by the Timer at the end of the vote's duration, or by the
	 * tallyVote method after each vote is collected, to determine 
	 * whether the vote is successful.  Calls onSuccess or onFailure(FailureReason).
	 */
	private synchronized void checkSuccess(boolean timeUp)
	{
		boolean success = false;
		boolean quorum = false;
		int voted = 0; // the number of participants that have voted so far
		for (Player player : participants)
			if (votes.get(player) != null)
				++voted;
		if (current.getQuorum() == Measure.ALL_QUORUM && voted == participants.size() ||
			current.getQuorum() == Measure.TWO_THIRDS_QUORUM && voted >= (2*participants.size())/3 ||
			current.getQuorum() == Measure.HALF_QUORUM && voted >= (participants.size())/2)
		{
			quorum = true;
			success = passed();
		}

		if (success && (timeUp || participants.size() == voted))
			onSuccess();
		else
			if (timeUp)
				onFailure(quorum? FailureReason.VOTE_FAILED: FailureReason.QUORUM_NOT_MET);

	}
	
	/**
	 * Returns true if the success criterion has been met.  Considers all votes that have
	 * been cast so far.
	 */
	private boolean passed()
	{
		int yeas = 0;
		for (Player player : participants)
			if (votes.get(player) != null && votes.get(player) == 1)
				++yeas;
		if (current.getSuccess() == Measure.SIMPLE_MAJORITY) 
			return yeas > participants.size()/2;
		else if (current.getSuccess() == Measure.TWO_THIRDS_MAJORITY) 
			return yeas >= (2*participants.size())/3;
		else if (current.getSuccess() == Measure.UNANIMOUS) 
			return yeas == participants.size();
		return false;
	}
	/**
	 * Called when the current measure is affirmed by vote with a quorum.  Clients
	 * are notified of the measure's success and the measure's Command is executed.
	 */
	private void onSuccess()
	{
		timer.cancel();
		try {
			gameServer.executeCommand(current.getCommand());
		} catch (commands.CommandException e) {
			e.printStackTrace();
		}
		current = null;
	}
	
	/**
	 * Called when the current measure fails.  The given FailureReason
	 * indicates the reason for the vote's failure.  Clients are notified of
	 * the measure's failure.
	 */
	private void onFailure(FailureReason f)
	{
		timer.cancel();
		current = null;
	}

	/**
	 * Returns the current measure being voted upon
	 */
	public Measure getCurrentMeasure() {
		return current;
	}

	/**
	 * Sets the current measure being voted upon and starts a timer for the vote.
	 * When the timer is up, checkSuccess will be called, and the success of the
	 * measure will be determined.
	 */
	public void setCurrentMeasure(Measure current) {
		this.current = current;

		timer.schedule( new TimerTask() {
			public void run() 
			{
				checkSuccess(true);
			}
		}, current.getDuration() * 1000);
	}
}